package IC.lir;

import java.util.*;

import util.LSortedSet;

import IC.lir.operands.Operand;
import IC.lir.operands.RegOperand;

import IC.lir.decl.DispatchVector;
import IC.lir.decl.LabelDecl;
import IC.lir.decl.StringDecl;
import IC.lir.instructions.*;


public class LIROptimizer implements Visitor<Object,Object>  {

	public static class OperandComparator implements Comparator<Operand> {

		int prio1, prio2;

		public int compare(Operand o1, Operand o2) {
			prio1 = o1.getPrio();
			prio2 = o2.getPrio();

			if(prio1 < prio2) {
				return -1;
			}
			else if(prio1 == prio2) {
				return 0;
			}
			else {
				return 1;
			}
		}
	}
	
	private class DoubleOperand extends Operand {

		private RegOperand 	instance;
		
		// can be only Reg or Imm
		private Operand 	field;
		
		public DoubleOperand(RegOperand instance, Operand field) {
			this.field = field;
			this.instance = instance;
		}
		
		public RegOperand getInstance() {
			return instance;
		}
		
		public Operand getField() {
			return field;
		}
		
		@Override
		public int getPrio() {
			return 2 << 10 + 1;
		}
		
		public String toString() {
			return instance.toString() + "." + field.toString();
		}
		
	}

	// output code
	private static List<LIREntry> code = new ArrayList<LIREntry>();

	LSortedSet<Operand> ls;
	
	private Comparator<Operand> comp;

	private LIROptimizer() {
		comp = new OperandComparator();
		ls = new LSortedSet<Operand>(comp);
	}


	public static List<LIREntry> Process(List<LIREntry> lir) {
		LIROptimizer p = new LIROptimizer();

		for (LIREntry e: lir) {
			e.accept(p, null);
		}

		return code;
	}


	public Object visit(LabelDecl that, Object d) {
		code.add(that);
		
		ls.reset();
		
		return null;
	}

	public Object visit(StringDecl that, Object d) {
		code.add(that);
		return null;
	}

	public Object visit(DispatchVector that, Object d) {
		code.add(that);
		return null;
	}

	public Object visit(ArrayLengthInstr that, Object context) {
		
		Operand op = that.getDest();
		ls.remove(op);
		
		code.add(that);
		return null;
	}

	public Object visit(JumpInstr that, Object context) {
		code.add(that);
		return null;
	}

	public Object visit(LibraryInstr that, Object context) {
		
		Operand op = that.getDest();
		ls.remove(op);
		
		code.add(that);
		return null;
	}

	public Object visit(MoveInstr that, Object context) {

		Operand dest = that.getDest();
		Operand source = that.getSource();

		Operand res = ls.appendToSet(source, dest);

		if(res != null) {
			if(!res.equals(dest)) {
				code.add(new MoveInstr(res, dest));
			} 
			else {
				// we saved a line of code
			}
		}
		else {
			code.add(that);
		}

		return null;
	}

	public Object visit(BasicInstr that, Object context) {
		// TODO:
		RegOperand destOp = that.getDest();
		ls.remove(destOp);
		
		Operand newSrc = ls.getMin(that.getSource());
		if(newSrc != null) {
			code.add(new BasicInstr(that.getOp(),newSrc, destOp));
		}
		else {
			code.add(that);
		}
		return null;
	}

	
	// *****   array instructions  *****//
	
	public Object visit(MoveToArrayInstr that, Object context) {
		/*
		DoubleOperand doubleOp = new DoubleOperand(that.getArray(), that.getIndex());
		
		Operand res = ls.appendToSet(that.getSource(), doubleOp);
		if(res != null && res instanceof DoubleOperand) {
			doubleOp = (DoubleOperand)res;
			code.add(new MoveToArrayInstr(that.getSource(), doubleOp.getInstance(), doubleOp.getField()));
		}
		else {
			code.add(that);
		}
		*/
		code.add(that);
		return null;
	}

	public Object visit(MoveFromArrayInstr that, Object context) {
		
		Operand op = that.getDest();
		ls.remove(op);

		code.add(that);
		return null;
	}

	//*****   field instructions  *****//
	
	public Object visit(MoveToFieldInstr that, Object context) {
		code.add(that);
		return null;
	}

	public Object visit(MoveFromFieldInstr that, Object context) {
		Operand op = that.getDest();
		ls.remove(op);

		code.add(that);
		return null;
	}
	
	//***** method instruction *****//

	public Object visit(ReturnInstr that, Object context) {
		code.add(that);
		return null;
	}

	public Object visit(StaticCallInstr that, Object context) {
		
		Operand op = that.getDest();
		ls.remove(op);
		
		code.add(that);
		return null;
	}

	public Object visit(VirtualCallInstr that, Object context) {
		
		Operand op = that.getDest();
		ls.remove(op);
		
		code.add(that);
		return null;
	}



	/*
	public static void main(String[] args) {
		Map<Operand, Integer> map = new HashMap<Operand, Integer>();;
		Comparator c = new OperandComparator();
		TreeSet<Operand> set1 = new TreeSet<Operand>(c);

		set1.add(new RegOperand());
		set1.add(new RegOperand());
		set1.add(new IntImmediate());
		set1.add(new MemOperand(null));
		set1.add(new IntImmediate());
		set1.add(new RegOperand());

		for(Operand op : set1) {
			map.put(op, 0);
		}

		TreeSet<Operand> set2 = new TreeSet<Operand>(c);
		RegOperand reg = new RegOperand();
		set2.add(reg);
		set2.add(new RegOperand());
		IntImmediate imm = new IntImmediate(4);
		set2.add(imm);
		MemOperand mem = new MemOperand(null);
		set2.add(mem);
		set2.add(new IntImmediate());
		set2.add(new RegOperand());

		for(Operand op : set2) {
			map.put(op, 1);
		}

		List<SortedSet<Operand>> list = new ArrayList<SortedSet<Operand>>();

		list.add(set1);
		list.add(set2);

		System.out.println(imm);
		System.out.println(list.get(map.get(mem)).first());

	}
	 */

}
